﻿using System.Collections.Generic;
using System.Threading;
using System.IO;
using Ionic.Zip;
using System;
using System.Text;
using Ionic.Zlib;

namespace Framework
{
    public enum UnzipStatus : int
    {
        None,
        /// <summary>
        ///  正在解压
        /// </summary>
        Running,
        /// <summary>
        ///  解压完成
        /// </summary>
        Done,
        /// <summary>
        ///  文件不存在
        /// </summary>
        FileNotFound,
        /// <summary>
        ///  磁盘空间不足
        /// </summary>
        NotEnoughSpace,
        /// <summary>
        ///  线程退出
        /// </summary>
        ThreadAbort,
        /// <summary>
        ///  异常
        /// </summary>
        Exception,
    }
    public sealed class UnityZipUtil
    {
        internal static readonly DateTime ConstModifiedTime = new DateTime(2024, 4, 30, 0, 0, 0, DateTimeKind.Utc);

        /// <summary>
        /// 解压某个文件到某个文件夹
        /// </summary>
        /// <param name="zipFilePath">zip文件路径</param>
        /// <param name="toDirPath">解压后的文件存放目录</param>
        /// <param name="zipfileNames">解压后的文件列表</param>
        /// <param name="extractProgress">解压进度回调</param>
        /// <returns></returns>
        public static UnzipStatus UnZipFile(string zipFilePath, string toDirPath, ref float progress, out List<string> zipfileNames)
        {
            zipfileNames = new List<string>();

            if (!FileHelper.IsFileExists(zipFilePath))
            {
                return UnzipStatus.FileNotFound;
            }
            toDirPath = FileHelper.GetUnityPath(toDirPath);
            if (toDirPath.CustomEndsWith("/"))
            {
                toDirPath = toDirPath.Substring(0, toDirPath.Length - 1);
            }
            FileHelper.EnsureDir(toDirPath);
            string filePath = null;
            try
            {
#if SL_DEBUG
                TimeWatch.Instance.Start();
#endif
                using (ZipFile zipFile = ZipFile.Read(zipFilePath))
                {
                    int idx = 0;
                    float interval = 1.0f / zipFile.Entries.Count;
                    foreach (var item in zipFile.Entries)
                    {
                        if (string.IsNullOrEmpty(item.FileName))
                        {
                            continue;
                        }
                        filePath = toDirPath + "/" + item.FileName;
                        filePath = FileHelper.GetUnityPath(filePath);
                        //如果是文件夹，创建目标文件夹就好了
                        if (filePath.CustomEndsWith("/"))
                        {
                            FileHelper.EnsureDir(filePath);
                            continue;
                        }
                        FileHelper.EnsureDirForFile(filePath);
                        zipfileNames.Add(item.FileName);
                        if (idx % 100 == 0)
                        {
                            GC.Collect();
                        }
                        FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write);
                        try
                        {
                            item.Extract(fs);
                            idx++;
                            progress = idx * interval;
                        }
                        finally
                        {
                            fs.Flush();
                            fs.Close();
                        }
                    }
                }
#if SL_DEBUG
                Logger.Log($"UnityZipUtil.UnZipFile, zipFilePath:{zipFilePath} cost time: {TimeWatch.Instance.Watch()} s!");
#endif
            }
            catch (ThreadAbortException)
            {
                Logger.Log("UnityZipUtil.UnZipFile, thread was aborted by other thread!");
                return UnzipStatus.ThreadAbort;
            }
            catch (System.Exception exp)
            {
                //磁盘满了，先把文件删除
                if (exp.Message.Contains("Disk full"))
                {
                    FileHelper.DeleteFile(filePath);
                    Logger.LogError("UnityZipUtil.UnZipFile error, Disk full deleteFile: " + filePath);
                }

                Logger.LogException(exp);
                Logger.LogError("UnityZipUtil.UnZipFile error, zipFilePath = " + zipFilePath + " cur extract file = " + filePath);
                zipfileNames.Clear();
                if (exp.Message.Contains("Disk full"))
                {
                    return UnzipStatus.NotEnoughSpace;
                }
                return UnzipStatus.Exception;
            }
            finally
            {
                GC.Collect();
                GC.Collect(1);
            }
            return UnzipStatus.Done;
        }

        public static byte[] ZipStr(string str)
        {
            var bytes = Encoding.UTF8.GetBytes(str);
            return ZipBuffer(bytes);
        }

        public static string UnzipStr(byte[] data)
        {
            var bytes = UnzipBuffer(data);
            return Encoding.UTF8.GetString(bytes);
        }

        public static byte[] ZipBuffer(byte[] data)
        {
            using (var msi = new MemoryStream(data))
            using (var mso = new MemoryStream())
            {
                using (var gs = new GZipStream(mso, CompressionMode.Compress))
                {
                    StreamCopyTo(msi, gs);
                }

                return mso.ToArray();
            }
        }

        /// <summary>
        /// 生成gzip数据,并固定压缩包内的时间戳数据, 通常用于生成补丁文件用
        /// </summary>
        /// <param name="data">未压缩的数据</param>
        /// <returns>压缩后的数据</returns>
        public static byte[] ZipBufferWithConstModifyTime(byte[] data)
        {
            using (var msi = new MemoryStream(data))
            {
                using (var mso = new MemoryStream(data.Length))
                {
                    using (var gs = new GZipStream(mso, CompressionMode.Compress))
                    {
                        gs.LastModified = ConstModifiedTime;
                        msi.CopyTo(gs);
                    }

                    return mso.ToArray();
                }
            }
        }
        
        public static byte[] UnzipBuffer(byte[] data)
        {
            using (var msi = new MemoryStream(data))
            using (var mso = new MemoryStream())
            {
                using (var gs = new GZipStream(msi, CompressionMode.Decompress))
                {
                    StreamCopyTo(gs, mso);
                }

                return mso.ToArray();
            }
        }
        private static readonly byte[] CopyBuffer = new byte[4096];
        private static void StreamCopyTo(Stream src, Stream dest)
        {
            int cnt;
            while ((cnt = src.Read(CopyBuffer, 0, CopyBuffer.Length)) != 0)
            {
                dest.Write(CopyBuffer, 0, cnt);
            }
        }
    }
}
